Lovely gopher

Dependency injection in Golang

Matt Tarasov
FAUN — Developer Community 🐾
3 min readJun 1, 2022

--

Today I am going to talk about Dependency Injection in Golang.

What is it?

DI (Dependency Injection) is a technique when your modules receive dependency indirectly. They don’t know about the implementation of dependency, only about the interface.

Why do we need it?

DI can help us to write low coupling code. It means that you would be able to exchange your code whenever you want, and it helps to reuse some of their parts.

Example

We have a tiny project: main.go and two services — logger and cache

├───cmd
│ └───main.go
└───services
├───logger
│ └───logger.go
└───cache
└───cache.go

How do they work? We have a cache service which can be used if you want to store something in fast storage like Redis.

package cache// interface of the logger service which will be injected
// and a good tone to write it in lowercase to hide export
type logger interface {
Error(error)
Info(string)
}
type Cache struct {
logger logger
}
// constructor of our service which recieve interfaces of
// services which will be injected (we can inject a few services) and
// return struct (instance of cache)
func NewCache(logger logger) *Cache {
return &Cache{
logger: logger,
}
}
// some methods
func (r *Cache) Get(key string) (*string, error) {
// ...
}
func (r *Cache) Set(key string, data []byte) error {
// ...
}

As you may see, the Cache service knows nothing about logger except the method that Cache needs. We don’t import anything, just describe the interface with two methods, Error(error) and Info(string).

Look at the Logger:

package logger// sentry client interface for service wich to be injected
type sentryClient interface {
// only one method for the all client (with maybe tens methods)
// because we need only one and the logger know only about one
sendMessage(interface{})
}
type Logger struct {
sentry sentryClient
}
// constructor
func NewLogger(sentryClient sentryClient)* Logger{
return &Logger{
sentry: sentryClient,
}
}
// we can see four methods, but Cache know only about two
func (l *Logger) Error(e error) {}
func (l *Logger) Info(msg string) {}
func (l *Logger) Debug(e error) {}
func (l *Logger) Log(e error) {}

So, now we have to use it all together in main.go:

package main
import logger "gopath/project/logger"
import cache "gopath/project/cache"
func main() {
// register services
sentryService := NewSomeSentryService()
loggerService := logger.NewLogger(sentryService)
authService := cache.NewCache(loggerService)
// ... init some other handlers and services
}

Let’s summarize

We've created two low coupled services which can work with any other Loggers and SentryClients, for example, we can use logger for tests that write to file or stdout.

Bonus

If you want to write tests, DI also can help you! Sometimes you need mocks for tests (to be independent of some modules like a logger in tests), you have to generate them! There is an amazing tool for this task in go — GoMock. When you describe only needed methods in DI interface, the GoMock generate mocks just for those methods — it is faster and more readable in practice than hundreds of methods in mocks.

If this post was helpful, please click the clap 👏 button below a few times to show your support for the author 👇

🚀Developers: Learn and grow by keeping up with what matters, JOIN FAUN.

--

--

Frontend developer. React and Typescript. Sometimes Golang. Also, I love to create cool things like games, scrappers, browser’s extensions